package gl.java.game;

import com.sun.istack.internal.Nullable;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class GameEngine implements Timer.ThreadBinder {
    public static final int MIN_WAIT_NS = 1 * 1000 * 1000;
    private NetEventReceiver mNetWorkPacketReceiver;

    private static Logger Log = LoggerFactory.getLogger(GameEngine.class);

    private volatile boolean isStart = true;
    private Lock lock = new ReentrantLock();
    private Condition idleCondition = lock.newCondition();
    private Queue<NetEvent> queue = new ConcurrentLinkedQueue<NetEvent>();
    private Queue<Runnable> timerEventQueue = new ConcurrentLinkedQueue<Runnable>();
    Timer timer;

    @Override
    public void onDeliver(Runnable runnable) {
        timerEventQueue.offer(runnable);
        notifyLoopThread();
    }


    private static class Holder {
        private static GameEngine engine = new GameEngine();
    }

    //TODO 初始化多个GameEngine实例按房间分配.在保证业务线程安全的前提下.利用多线程在多CPU核心的硬件上来提高GameEngine的性能
    public static GameEngine getInstance() {
        return Holder.engine;
    }

    private GameEngine() {
        timer = new Timer();
        new Thread("engine") {
            @Override
            public void run() {
                log.info("engine loop start");
                while (isStart) {
                    long current = System.nanoTime();
                    try {
                        loopNetWorkEvent();
                    } catch (Exception e) {
                        log.error("loopNetWorkEvent error " + e);
                    }
                    try {
                        loopTimer();
                    } catch (Exception e) {
                        log.error("loopTimer error ", e);
                    }
                    long takeTime = System.nanoTime() - current;//note:window xp sp2的多核CPU上可能返回负数.
                    if (takeTime - current < MIN_WAIT_NS) {
                        try {
                            lock.lock();
                            idleCondition.await(MIN_WAIT_NS, TimeUnit.NANOSECONDS);
                        } catch (InterruptedException e) {
                        } finally {
                            lock.unlock();
                        }
                    }
                    if (takeTime > 50000000) {
                        log.info("busy up, takeTime {} ms", takeTime / 1000000);
                    }
                }
                log.info("engine loop stop");
            }
        }.start();

    }

    private void loopNetWorkEvent() {
        while (!queue.isEmpty()) {
            NetEvent event = queue.poll();
//            log.info("loopNetWorkEvent ");
            if (queue != null && this.mNetWorkPacketReceiver != null) {
                this.mNetWorkPacketReceiver.onReceiver(event);
            }
        }
    }

    public boolean addNetWorkEvent(NetEvent p) {
        boolean offer = this.queue.offer(p);
//        log.info("addNetWorkEvent");
        notifyLoopThread();
        return offer;
    }

    private void notifyLoopThread() {
        try {
            lock.lock();
            idleCondition.signal();
        } finally {
            lock.unlock();
        }
    }

    public void setNetWorkEventReceiver(NetEventReceiver receiver) {
        this.mNetWorkPacketReceiver = receiver;
    }

    private long loopTimer() {
        while (!timerEventQueue.isEmpty()) {
            timerEventQueue.poll().run();
        }
        return 0;
    }

    public long setTimeout(@Nullable Runnable runnable, long millisecond) {
        return timer.setTimeout(runnable, millisecond, this);
    }


    public long setInterval(@Nullable Runnable runnable, long millisecond) {
        return timer.setInterval(runnable, millisecond, this);
    }

    public void clearInterval(@Nullable long id) {
        timer.clearInterval(id);
    }

    public void clearTimeout(@Nullable long id) {
        timer.clearTimeout(id);
    }

    public void stop() {
        notifyLoopThread();
        isStart = false;
        timer.clearAll();
        log.warn("engine has stop , the network.queue.size:{} ",queue.size());
        queue.clear();
        mNetWorkPacketReceiver = null;
    }
}
